1

最近接到一个任务,需要判断传过来的 fd 是不是属于 eventfd/signalfd 这一类特定的 fd。因为这一类 fd 不支持某些操作,如果调用时不加判断,会报 Invalid Argument 错误。按理说,如果能把 fd 类型作为一个额外的参数传进来,就能轻松解决问题了。不过因为一些限制,拿到手时只有 fd 这一个整数。好在需要过滤的地方不在重要的路径上,所以 hack 一点的办法应该也能被接受。

既然 Linux 能够根据一个整数 fd 调用特定的操作,那么内核层面上一定会有一个 fd 到实际文件类型的映射。所以最好的打算,就是 Linux 把这个映射通过 syscall 的方式暴露出来了,然后我只要调用它,就能完成工作了。可惜找了一段时间,依然没有找到对应的 syscall。看来是不存在这样的好事了。

补充一个前提,我正在编写的是 C 代码。如果写的是脚本的话,至少有两种方法可以获取特定 fd 的类型。

一种是调用 lsof -p $pid -a -d $fd

 ¥ lsof -p 16166 -a -d 15
COMMAND   PID USER   FD      TYPE             DEVICE SIZE/OFF       NODE NAME
xxxxx   16166  lzx   15u  a_inode               0,13        0      10094 [eventfd]

我们可以看到 进程 16166 的第 15 号 fd 是一个 eventfd。

另一种是调用 cat /proc/$pid/fdinfo/$fd

 ¥ cat /proc/16166/fdinfo/16
pos:    0
flags:    02004002
mnt_id:    13
sigmask:    0000000000010000

该操作并不会直接显示 fd 的类型。但是因为不同类型的 fdinfo 格式不一样,你可以根据 man procfs 里面的说明,解析出具体 fd 的类型。比如 进程 16166 的第 16 号 fd 是一个 signalfd。

毫无疑问,对于 C 程序,上面两种方法都不太合适。虽说并不是无法实现,但是你让一个 C 程序每次判断 fd 类型时都要读完整个 fdinfo 文件,然后再通过一个 parser 解析出具体的 fd 类型,无论从性能上还是代码量上都不可接受。

也许存在某个 API,可以直接获取 fd 类型?为什么不看看神奇的 lsof 的源码呢?

抱着源码里可能隐藏着谜底的想法,我下载了 lsof 的代码。打开底下一个名为 linux 的文件夹,看了里面的文件几眼,我感觉被欺骗了。

没想到 lsof 你这个浓眉大眼的,居然也是用读 /proc 的方式来获取 fd 信息的!
看来这条路也断了。难道真的非得去解析 fdinfo 不可吗……

仔细看多几眼,lsof 貌似只是从 /proc/$pid/fd 里面读取了基本的信息。有些信息,比如我想要的 NAME,不是直接从那里面获取的。看来还是有希望的。我试着找找看,看看有什么关键的操作。

以下略去一些曲折的碰壁和反复的试错…… 反正我费了若干个小时,走了些弯路,终于明白汲汲以求的 NAME 是怎么得到的。其实就是 readlink /proc/$pid/fd/$fd!

对于 regular file,readlink /proc/$pid/fd/$fd 返回的是文件路径。而对于 eventfd/signalfd 这一类不可能有真正的 inode 的文件,返回的是 anon_inode:[eventfd]。于是我需要做的事情相对简单了:
先拼接 readlink 的文件名参数,然后调用 readlink,解析返回来的文件名。由于我只需要知道某个整数 fd 在本进程内的类型,可以用 /proc/self 代替 /proc/$pid。这么一来,我就只需要拼接后面的 $fd 部分。因为 fd 是一个整数,整个文件名 buffer 大小是有上限的,而且这个上限不大。所以可以把这个 buffer 弄成静态分配的,不用操心内存管理。接下来是 readlink 这个系统调用,原来最好的打算里面,也是逃不开一次系统调用的,所以这部分不算额外的开销。最后是对返回文件名的解析,凭借该文件名有着固定前缀的特性,我们可以先 strncmp 判断是否是 anon_inode:[,接着一路解析出方括号内具体的 fd 类型。也不是很难就是了。


后来看了相关的内核代码,估计应该不会有更好的方法了。

下面是 eventfd 创建代码:

    /* fs/eventfd.c */
    struct file *file;
    ...
    file = anon_inode_getfile("[eventfd]", &eventfd_fops, ctx,
                  O_RDWR | (flags & EFD_SHARED_FCNTL_FLAGS));

你可以这么认为,anon_inode_get_file 返回了 file 的一个实例,而这个实例组合了 eventfd_fops 这个 eventfd 功能的具体实现。eventfd 之所以跟其他 fd 不同,全靠 eventfd_fops 里面定义的方法。如果内核要想提供一个 API,返回某个 fd 的类型,最好是每个 fd 都实现返回自己的类型方法。但是 eventfd_fops 这个结构体里面,没法返回 eventfd 标识的方法。有一个 show_fdinfo 的方法,不过这个方法是提供给 /proc/$pid/fdinfo 展示用的。另外 eventfd_fops 这个结构体是 static 的,所以也排除存在 if (file_operation == &eventfd_fops) return enum_eventfd; 这样的代码的可能性。

signalfd 的情况也差不多。


最后我没有采用 readlink 的方式,而是改由创建特殊 fd 的代码顺便把 fd 编号记录下来,接收到 fd 之后,我再去查找,看看是否是特殊的 fd。


spacewander
5.6k 声望1.5k 粉丝

make building blocks that people can understand and use easily, and people will work together to solve the very largest problems.